package org.deltaset.secured.menu;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import javax.persistence.Entity;

/**
 *
 * @author dmakariev
 */
class ClasspathScanner {

    private final Handler handler;

    private ClasspathScanner(Handler handler) {
        this.handler = handler;
    }

    public static List<Class> getEntityClasses(ClassLoader classLoader, String packageName, boolean recursive) {
        final EntityClassHandler entityClassHandler = new EntityClassHandler(classLoader, packageName, recursive);
        final ClasspathScanner classpathScanner = new ClasspathScanner(entityClassHandler);
        classpathScanner.scanClasspath();
        return entityClassHandler.getEntityClasses();

    }


    private void scanClasspath()  {

        final ClassLoader classLoader = handler.getClassLoader();
        if (!(classLoader instanceof URLClassLoader)) {
            return;
        }

        final URL[] urls = ((URLClassLoader) classLoader).getURLs();
        final Set alreadyScanned = new HashSet();

        for (URL url : urls) {
            final String urlPath = cleanUrlPath(url);
            final File file = new File(urlPath);
            if (alreadyScanned.contains(file.getAbsolutePath())) {
                //already scanned
                continue;
            }
            if (file.isDirectory()) {
                handleDirectory(file, null);
            } else {
                handleArchive(file);
            }
            alreadyScanned.add(file.getAbsolutePath());
        }
    }

    private static String cleanUrlPath(final URL url)  {
        
        String urlPath = url.getFile();
        try {
            urlPath = URLDecoder.decode(urlPath, "UTF-8");
        } catch (UnsupportedEncodingException ex) {
            Logger.getLogger(ClasspathScanner.class.getName()).log(Level.SEVERE, null, ex);
        }
        if (urlPath.startsWith("file:")) {
            urlPath = urlPath.substring(5);
        }

        if (urlPath.indexOf('!') > 0) {
            urlPath = urlPath.substring(0, urlPath.indexOf('!'));
        }
        return urlPath;
    }

    private void handleArchive(final File file) {
        try {            
               final ZipFile zip = new ZipFile(file);
               final Enumeration<? extends ZipEntry> entries = zip.entries();

               while (entries.hasMoreElements()) {
                   final ZipEntry entry = entries.nextElement();
                   final String name = entry.getName();                
                   handler.handle(name);
               }
        } catch (ZipException ex) {
            Logger.getLogger(ClasspathScanner.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ClasspathScanner.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void handleDirectory(File file, String path) {     
        for (File child : file.listFiles()) {
            final String newPath = path == null ? child.getName() : path + '/' + child.getName();

            if (child.isDirectory()) {
                handleDirectory(child, newPath);
            } else {
                handler.handle(newPath);
            }
        }
    }

    private interface Handler {

        void handle(String path);

        ClassLoader getClassLoader();
    }

    private static class EntityClassHandler implements Handler {

        public static final String CLASS_SUFIX = ".class";
        private final ClassLoader classLoader;
        private final String packagePath;
        private final boolean recursive;
        private final List<Class> entityClasses = new ArrayList<Class>();

        private EntityClassHandler(ClassLoader classLoader, String packageName, boolean recursive) {
            this.classLoader = classLoader;
            this.packagePath = packageName.replace(".", "/");
            this.recursive = recursive;
        }

        @Override
        public void handle(String path) {          
            if (path.startsWith(packagePath)) {
                if (true == recursive) {
                    addToList(path);

                } else {
                    final boolean hasSubfolder = containsSubFolder(path, packagePath);
                    if (false == hasSubfolder) {
                        addToList(path);
                    }
                }
            }
        }

        private static boolean containsSubFolder(final String path, final String packagePath) {
            final String subPath = path.substring(packagePath.length());
            if (subPath.length() < 1) {
                return false;
            }
            return subPath.substring(1).indexOf("/") != -1;
        }

        private void addToList(String path) {
            try {
                if (path.endsWith(CLASS_SUFIX)) {
                    final String className = path.replace("/", ".").substring(0, path.length() - CLASS_SUFIX.length());
                    final Class clazz = Class.forName(className);
                    if (null != clazz.getAnnotation(Entity.class)) {
                        entityClasses.add(clazz);
                    }
                }
            } catch (ClassNotFoundException ex) {
            }
        }

        List<Class> getEntityClasses() {
            return entityClasses;
        }

        @Override
        public ClassLoader getClassLoader() {
            return classLoader;
        }
    }
}